namespace RL

open Garnet.Composition
open RL.Components
open RL.FieldOfVision
open System.Collections.Generic

module Action =
    type ActionType =
        | Awaiting
        | ShortWait
        | Move of destination : Point2D
        | Attack of target : Eid

    type Action = { actionType : ActionType; cost : int; initialCost : int }
    type ActionPoints = { ap : int; priority : int }

    module ActionDefs =
        let AwaitAction = { actionType = ActionType.Awaiting; cost = 0; initialCost = 0 }
        let ShortWait = { actionType = ActionType.ShortWait; cost = 25; initialCost = 25 }
        let Move p = { actionType = ActionType.Move(p); cost = 100; initialCost = 100 }
        let Attack t = { actionType = ActionType.Attack(t); cost = 100; initialCost = 100 }

    let internal PerformAction(world : Container, eid : Eid, action : ActionType) =
        let entity = world.Get(eid)
        match action with
            | Move(d) ->
                entity.Set(d)
                if entity.Has<FieldOfVision>() then
                    let fov = entity.Get<FieldOfVision>()
                    fov.Dirty <- true
            | Attack(t) ->
                let target = world.Get(t)
                printfn "%s attacks %s!" (entity.Get<string>()) (target.Get<string>())
            | _ -> ()

    type ProcessActions = struct end
    type AIProcessActionTrigger = { e : Entity }

    type Container with
        member c.RegisterActionSystem =
            c.On<ProcessActions> <| fun _ ->
                let playerID = (c.Query<Eid, Player>() |> Seq.exactlyOne).Value1

                // Build a priority queue of all actors, sorted by remaining action cost minus the actor's priority number.
                let actionQueue = PriorityQueue()
                for e in c.Query<Eid, Action, ActionPoints>() do
                    actionQueue.Enqueue(e.Value1, e.Value2.cost - e.Value3.priority)
                    // Print statement for debugging. Uncomment when needed.
                    //printfn
                    //    "ID : %i, Name : %s, Prio : %i, Cost : %i, AfterAP : %i QPos : %i"
                    //    e.Value1.Value (c.Get(e.Value1).Get<string>()) e.Value3.priority e.Value2.cost (e.Value2.cost - e.Value3.ap) (e.Value2.cost - e.Value3.priority)

                let mutable playerAwaiting = false

                // Loop until the queue is empty.
                while actionQueue.Count > 0 do
                    let _, eid, cost = actionQueue.TryDequeue()
                    let entity = c.Get(eid)
                    let action = entity.Get<Action>()
                    let points = entity.Get<ActionPoints>()
                    let trueCost = cost + points.priority

                    if trueCost > 0 then
                        if action.cost - points.ap > 0 then
                            // If the action can't be performed now,
                            // reset the action with the remaining cost decremented by the entity's action points.
                            if eid = playerID then
                                entity.Set({ actionType = action.actionType; cost = action.cost - points.ap; initialCost = action.initialCost })
                            else
                                c.Run <| { e = entity }
                        else
                            // Otherwise, perform the action and set to await.
                            PerformAction(c, eid, action.actionType)
                            entity.Set(ActionDefs.AwaitAction)
                            if eid = playerID then playerAwaiting <- true
                    else
                        match action.actionType with
                            | Awaiting ->
                                if eid = playerID then
                                    // Store that the player is awaiting input.
                                    playerAwaiting <- true
                                else
                                    // Since the AI is waiting, let it figure out its next action, and requeue it.
                                    c.Run <| { e = entity }
                                    let action = entity.Get<Action>()
                                    actionQueue.Enqueue(eid, action.cost - points.priority)
                            | _ ->
                                PerformAction(c, eid, action.actionType)    // Perform the action.
                                entity.Set(ActionDefs.AwaitAction)                       // Set the actor to await a new action.
                    c.Run <| FOVProcess()   // Run the FOV process at the end of each action.

                // If the player is not able to act yet, run it again!
                if not playerAwaiting then c.Run <| ProcessActions()

    let init(c : Container) =
        Disposable.Create[c.RegisterActionSystem] |> ignore
